#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>

#define BUFFER_SIZE 4096
#define ERROR_CHECK(ret, num, msg) \
    {                              \
        if (ret == num)            \
        {                          \
            perror(msg);           \
            exit(1);               \
        }                          \
    }
#define ARGS_CHECK(argc, num)                 \
    {                                         \
        if (argc < num)                       \
        {                                     \
            fprintf(stderr, "args error!\n"); \
            return -1;                        \
        }                                     \
    }
typedef struct filemsg
{
    char file_name[256];
    char file_permission[10];
    char file_type;
    unsigned long size;
} FMSG;
char getFileType(__mode_t mode)
{
    if (S_ISDIR(mode))
        return 'd';
    if (S_ISBLK(mode))
        return 'b';
    if (S_ISCHR(mode))
        return 'c';
    if (S_ISFIFO(mode))
        return 'p';
    if (S_ISLNK(mode))
        return 'l';
    if (S_ISSOCK(mode))
        return 's';
    if (S_ISREG(mode))
        return '-';
    return '?';
}
char *get_permissions(__mode_t mode)
{
    static char permissions[10];
    strcpy(permissions, "rwxrwxrwx");
    permissions[9] = '\0';
    for (size_t i = 0; i < 9; i++)
    {
        if (!(mode & 1 << (8 - i))) // mode &100  ; mode & 010 ; mode & 001;
        {
            permissions[i] = '-';
        }
    }

    return permissions;
}
void archive_directory(FMSG *filemsg, char *destfile)
{
    DIR *dir = opendir(filemsg->file_name);
    struct dirent *entry;
    struct stat st;
    FILE *fp = fopen(destfile, "a+");
    ERROR_CHECK(NULL, fp, "open");
    fprintf(fp, "BEN %s %ld %s %c EFF\n", filemsg->file_name, filemsg->size, filemsg->file_permission, filemsg->file_type);
    while (entry = readdir(dir))
    {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
        {
            continue; // 忽略当前目录和父目录
        }
        int ret = stat(entry->d_name, &st);
        // 如果是目录，则递归复制
        if (S_ISDIR(st.st_mode))
        {
            struct stat statBuf;
            int ret = stat(entry->d_name, &statBuf);
            ERROR_CHECK(-1, ret, "stat");
            FMSG filemsg;
            filemsg.file_type = getFileType(statBuf.st_mode);
            strcpy(filemsg.file_name, entry->d_name);
            strcpy(filemsg.file_permission, get_permissions(statBuf.st_mode));
            filemsg.size = statBuf.st_size;
            archive_directory(&filemsg, destfile);
        }
        // 如果是文件，则直接复制
        else
        {
            FILE *in, *out;
            in = fopen(entry->d_name, "rb");
            if (!in)
            {
                perror("fopen");
                closedir(dir);
                return;
            }

            out = fopen(destfile, "wb");
            if (!out)
            {
                perror("fopen");
                fclose(in);
                closedir(dir);
                return;
            }

            char buffer[BUFFER_SIZE];
            size_t n;
            while ((n = fread(buffer, 1, BUFFER_SIZE, in)) > 0)
            {
                if (fwrite(buffer, 1, n, out) != n)
                {
                    perror("fwrite");
                    fclose(in);
                    fclose(out);
                    closedir(dir);
                    return;
                }
            }

            if (ferror(in))
            {
                perror("fread");
                fclose(in);
                fclose(out);
                closedir(dir);
                return;
            }

            fclose(in);
            fclose(out);
        }
    }
}
void archive_file(FMSG *filemsg, char *destfile)
{
    FILE *fp_in = fopen(destfile, "a+");
    ERROR_CHECK(NULL, fp_in, "fopen");
    fprintf(fp_in, "BEN %s %ld %s %c EFF\n", filemsg->file_name, filemsg->size, filemsg->file_permission, filemsg->file_type);
    FILE *fp_out = fopen(filemsg->file_name, "rb");
    ERROR_CHECK(NULL, fp_out, "fopen");
    char buffer[BUFFER_SIZE];
    size_t n;
    while ((n = fread(buffer, 1, BUFFER_SIZE, fp_out)) > 0)
    {
        if (fwrite(buffer, 1, n, fp_in) != n)
        {
            perror("fwrite");
            fclose(fp_in);
            fclose(fp_out);
            return;
        }
    }
    fclose(fp_in);
    fclose(fp_out);
}

void create_archive(char *destfile, char *srcfile)
{
    struct stat statBuf;
    int ret = stat(srcfile, &statBuf);
    ERROR_CHECK(-1, ret, "stat");
    FMSG filemsg;
    filemsg.file_type = getFileType(statBuf.st_mode);
    strcpy(filemsg.file_name, srcfile);
    strcpy(filemsg.file_permission, get_permissions(statBuf.st_mode));
    filemsg.size = statBuf.st_size;
    if ('d' == filemsg.file_type)
    {
        archive_directory(&filemsg, destfile);
    }
    else
    {
        archive_file(&filemsg, destfile);
    }
}
void extract_archive(char *srcfile)
{
    FILE *fpsrc = fopen(srcfile, "rb");
    char buf[1024];
    while (fgets(buf, sizeof(buf), fpsrc))

    {
        char *p = " \n";
        char *word = NULL;
        word = strtok(buf, p);
        char *filemsgin_srcfile[256] = {NULL};
        int index = 0;
        FMSG filemsg;
        if (word == "BEN")
        {
            while (word)
            {
                filemsgin_srcfile[index++] = word;
                word = strtok(NULL, p);
            }

            strcpy(filemsg.file_name, filemsgin_srcfile[1]);
            strcpy(filemsg.file_permission, filemsgin_srcfile[3]);
            filemsg.size = atoi(filemsgin_srcfile[2]);
            filemsg.file_type = *filemsgin_srcfile[4];
            printf("%s %ld %s %c\n",filemsg.file_name,filemsg.size,filemsg.file_permission,filemsg.file_type);
            if (filemsg.file_type == 'd')
            {
            }
            else
            {
                FILE *fpdest = fopen(filemsg.file_name, "wb+");
                char ch = '0';

                for (size_t i = 0; i < filemsg.size; i++)
                {
                    
                    ch = fgetc(fpsrc);
                    printf("%c ",ch); 
                    fputc(ch, fpdest);
                }
                puts("");
            }
        }
    }
}
int main(int argc, char *argv[])
{

    if (strcmp(argv[1], "c") == 0)
    {
        ARGS_CHECK(argc, 3);
        for (size_t i = 0; i < argc - 3; i++)
        {
            create_archive(argv[2], argv[3]);
        }
    }
    else if (strcmp(argv[1], "x") == 0)
    {
        if (argc != 3)
        {
            perror("argcs");
            exit(1);
        }

        extract_archive(argv[2]);
    }
    else
    {
        fprintf(stderr, "Invalid operation. Use 'create' or 'extract'.\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}
